# ExtraWarnings.cmake

# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
# https://clang.llvm.org/docs/DiagnosticsReference.html

include(CheckCXXCompilerFlag)
include(CMakePushCheckState)

# 防止重复包含该模块，避免重复追加警告或 sanitizer 选项。
if(DEFINED EXTRA_WARNINGS_INCLUDED)
    return()
endif()
set(EXTRA_WARNINGS_INCLUDED TRUE)

# -------------------------------
# Sanitizer 开关项
# -------------------------------
# 通过 CMake option 允许调用者按需启用各类 Sanitizer。
option(ENABLE_ASAN  "Enable AddressSanitizer"              OFF) # 地址越界/野指针检测
option(ENABLE_MSAN  "Enable MemorySanitizer"               OFF) # 未初始化内存读取检测
option(ENABLE_TSAN  "Enable ThreadSanitizer"               OFF) # 数据竞争检测
option(ENABLE_UBSAN "Enable Undefined Behavior Sanitizer"  OFF) # 未定义行为检测
option(ENABLE_LSAN  "Enable Leak Sanitizer"                OFF) # 内存泄漏检测

# -------------------------------
# 帮助函数：检测编译器是否支持某个 flag
# -------------------------------
function(_compiler_supports_flag flag out_var)
    if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
        set(${out_var} FALSE PARENT_SCOPE)
        return()
    endif()

    cmake_push_check_state(RESET)
    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror=unknown-warning-option -Werror=unused-command-line-argument")
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror")
    endif()

    check_cxx_compiler_flag("${flag}" ${out_var})
    cmake_pop_check_state()

    set(${out_var} ${${out_var}} PARENT_SCOPE)
endfunction()

# -------------------------------
# 主函数：为目标追加额外警告与 Sanitizer 选项
# -------------------------------
function(apply_extra_warnings_to_target TARGET_NAME)
    # 通用警告与安全相关编译选项（两家编译器普遍支持的子集）。
    set(COMMON_WARNING_FLAGS
        "-Wall"                            # 启用大部分常用警告
        "-Wextra"                          # 启用额外警告
        "-Wpedantic"                       # 严格遵循标准（抑制非标准扩展）
        "-Wconversion"                     # 警告隐式类型转换可能导致信息丢失
        "-Wsign-compare"                   # 警告有符号与无符号比较
        "-Wmissing-declarations"           # 函数定义缺少前置声明
        "-Wold-style-cast"                 # C 风格强制转换（C++ 中不鼓励）
        "-Wshadow"                         # 变量重名隐藏父作用域变量
        "-Wwrite-strings"                  # 将字符串字面量视为 const，防止被修改
        "-Wformat=2"                       # 更严格的 printf/scanf 格式检查
        "-Wcast-qual"                      # 转换时丢失 const/volatile 修饰符
        "-Wdeprecated-declarations"        # 使用过时 API
        "-fstack-protector-strong"         # 更强的栈保护（防止栈溢出攻击）
        "-fno-common"                      # 将未初始化全局变量放入 BSS，避免符号冲突
        "-D_FORTIFY_SOURCE=2"              # glibc 额外运行时检查（缓冲区溢出等）
        "-Wunused-result"                  # 对被标注为 warn_unused_result 的函数，如果返回值被忽略会警告。或者-Werror=unused-result：把上述警告升级为错误，强制调用方必须处理。
    )

    # 两家编译器大概率都支持，但仍做一次能力探测的选项。
    set(GENERIC_WARNING_FLAG_CANDIDATES
        "-Wzero-as-null-pointer-constant"  # 告警使用整数 0 作为空指针常量
        "-Wmissing-include-dirs"           # 指定的 include 目录不存在
        "-Wredundant-decls"                # 多次重复声明同一符号
        "-Wundef"                          # 未定义宏在 #if 中使用时报错
        "-Wswitch-default"                 # switch 语句缺少 default
        "-Wswitch-enum"                    # switch 未覆盖所有枚举值
        "-Wno-variadic-macros"             # 允许使用可变参数宏（结合 -Wpedantic）
        "-Wdisabled-optimization"          # 函数无法被优化时给出提示
        "-Wdouble-promotion"               # float 隐式转换为 double
        "-Wunused-macros"                  # 定义但未使用的宏
        "-Wmissing-format-attribute"       # 自定义 printf 函数未标注 format 属性
        "-Wsequence-point"                 # 序列点相关的潜在未定义行为
    )

    foreach(flag IN LISTS GENERIC_WARNING_FLAG_CANDIDATES)
        _compiler_supports_flag(${flag} SUPPORTS_FLAG)
        if(SUPPORTS_FLAG)
            list(APPEND COMMON_WARNING_FLAGS ${flag})
        endif()
    endforeach()

    # -------------------------------
    # Clang 额外诊断增强
    # -------------------------------
    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        list(APPEND COMMON_WARNING_FLAGS
            "-Weverything"                 # 启用 Clang 所有告警
            "-Wno-c++98-compat"            # 忽略 C++98 兼容性告警
            "-Wno-c++98-compat-pedantic"   # 同上，更严格模式
            "-Wno-weak-vtables"            # 忽略虚函数表未优化提示
            "-Wno-exit-time-destructors"   # 忽略全局析构函数告警
            "-Wno-global-constructors"     # 忽略全局构造函数告警
        )
        _compiler_supports_flag("-Wcomma" SUPPORTS_WCOMMA) # 检测逗号运算符误用
        if(SUPPORTS_WCOMMA)
            list(APPEND COMMON_WARNING_FLAGS "-Wcomma")
        endif()
    endif()

    # -------------------------------
    # GCC 额外诊断增强（仅在 GCC 下尝试）
    # -------------------------------
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        set(GCC_WARNING_FLAG_CANDIDATES
            "-Wlogical-op"                 # 检测逻辑运算中可能的笔误
            "-Wduplicated-cond"            # 条件判断重复
            "-Walloc-zero"                 # malloc(0) 等可疑行为
            "-Wduplicated-branches"        # switch/if 重复分支
            "-Wformat-signedness"          # printf/scanf 符号位不匹配
            "-Wtrampolines"                # 生成 trampoline 需可执行栈，提示风险
            "-Wmaybe-uninitialized"        # 更复杂路径的未初始化分析
            "-Wstrict-overflow=5"          # 有符号溢出被视为未定义行为
        )
        foreach(flag IN LISTS GCC_WARNING_FLAG_CANDIDATES)
            _compiler_supports_flag(${flag} SUPPORTS_FLAG)
            if(SUPPORTS_FLAG)
                list(APPEND COMMON_WARNING_FLAGS ${flag})
            endif()
        endforeach()
    endif()

    target_compile_options(${TARGET_NAME} PRIVATE
        $<$<COMPILE_LANGUAGE:C>:${COMMON_WARNING_FLAGS}>
    )

    # -------------------------------
    # C++ 专属警告
    # -------------------------------
    set(CXX_SPECIFIC_FLAGS
        "-Wnon-virtual-dtor"               # 含虚函数的类缺少虚析构函数
        "-Woverloaded-virtual"             # 覆盖函数签名不匹配，可能隐藏基类函数
    )

    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        set(CXX_WARNING_FLAG_CANDIDATES
            "-Weffc++"                     # Effective C++ 指南告警（Clang 17+ 支持）
            "-Wpointer-arith"              # 指针算术可疑行为
            "-Wctor-dtor-privacy"          # 构造/析构函数访问权限异常
            "-Wsuggest-override"           # 建议虚函数显式使用 override
        )
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        set(CXX_WARNING_FLAG_CANDIDATES
            "-Weffc++"                     # Effective C++ 指南告警
            "-Wpointer-arith"              # 指针算术可疑行为
            "-Wctor-dtor-privacy"          # 构造/析构函数访问权限异常
            "-Wstrict-null-sentinel"       # 模板/迭代器中的空指针哨兵检查
            "-Wnoexcept"                   # noexcept 函数可能抛出异常
            "-Wsuggest-override"           # 建议虚函数显式使用 override
        )
    else()
        set(CXX_WARNING_FLAG_CANDIDATES "")
    endif()

    foreach(flag IN LISTS CXX_WARNING_FLAG_CANDIDATES)
        _compiler_supports_flag(${flag} SUPPORTS_FLAG)
        if(SUPPORTS_FLAG)
            list(APPEND CXX_SPECIFIC_FLAGS ${flag})
        endif()
    endforeach()

    target_compile_options(${TARGET_NAME} PRIVATE
        $<$<COMPILE_LANGUAGE:CXX>:${COMMON_WARNING_FLAGS} ${CXX_SPECIFIC_FLAGS}>
    )

    # -------------------------------
    # Sanitizer 配置
    # -------------------------------
    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
        set(SANITIZER_KINDS "")
        set(SANITIZE_EXTRA_FLAGS
            "-fno-omit-frame-pointer"      # 保留帧指针，方便调试定位
            "-fno-optimize-sibling-calls"  # 关闭尾调用优化，确保堆栈完整
        )

        if(ENABLE_ASAN)
            list(APPEND SANITIZER_KINDS "address")    # AddressSanitizer
        elseif(ENABLE_MSAN)
            list(APPEND SANITIZER_KINDS "memory")     # MemorySanitizer
        elseif(ENABLE_TSAN)
            list(APPEND SANITIZER_KINDS "thread")     # ThreadSanitizer
        elseif(ENABLE_LSAN)
            list(APPEND SANITIZER_KINDS "leak")       # LeakSanitizer
        endif()

        if(ENABLE_UBSAN OR (SANITIZER_KINDS AND NOT ENABLE_MSAN))
            list(APPEND SANITIZER_KINDS "undefined")  # UndefinedBehaviorSanitizer
        endif()

        if(SANITIZER_KINDS)
            string(JOIN "," SANITIZER_COMBINED ${SANITIZER_KINDS})
            set(SANITIZE_FLAG "-fsanitize=${SANITIZER_COMBINED}")

            target_compile_options(${TARGET_NAME} PRIVATE
                ${SANITIZE_FLAG}
                ${SANITIZE_EXTRA_FLAGS}
            )
            target_link_options(${TARGET_NAME} PRIVATE
                ${SANITIZE_FLAG}
                ${SANITIZE_EXTRA_FLAGS}
            )

            message(STATUS "Enabled sanitizers for ${TARGET_NAME} in Debug build: ${SANITIZE_FLAG}; extra flags: ${SANITIZE_EXTRA_FLAGS}")
        endif()
    else()
        if(ENABLE_ASAN OR ENABLE_MSAN OR ENABLE_TSAN OR ENABLE_UBSAN OR ENABLE_LSAN)
            message(WARNING "Sanitizers are only enabled in Debug mode for ${TARGET_NAME}. Current build type: ${CMAKE_BUILD_TYPE}")
        endif()
    endif()

    # -------------------------------
    # 输出最终生效的编译选项，帮助调试配置。
    # -------------------------------
    get_target_property(TARGET_C_FLAGS ${TARGET_NAME} COMPILE_OPTIONS)
    if(TARGET_C_FLAGS)
        message(STATUS "Extra warnings enabled for ${TARGET_NAME}: ${TARGET_C_FLAGS}\n")
    endif()
endfunction()